<?php
class NICED_DBFactory 
{

	public static    $dsn_settings  = array();
	public static    $config        = array();
	protected static $_connections  = array();
	public static    $populated     = false;

	const DRIVER_TYPE_MYSQLI = 'mysqli';
	const DRIVER_TYPE_PDO    = 'pdo';

    private static $drivers = array(
            self::DRIVER_TYPE_MYSQLI,
            self::DRIVER_TYPE_PDO,
        );
	
	private function __construct() 
	{
        NICED_LOG(ERROR, NICED." ".__CLASS__." can not be instance");
	}

	//load dsn config from file to class
	//public static function setConfig($type, $settiongs) 
	//{
	//	self::$config[$type] = $settiongs;
	//}


	//resolve dsn and server config to dsn_settings
	public static function resolve($repopulated = false) 
	{
		if (self::$populated && !$repopulated) 
		{
			return true;
		}

		if (!($dsn_config = LC::get("dsn")) || !($server_config = LC::get("server")))
		{
            NICED_LOG(ERROR, NICED." no dsn or server config");
			return false;
		}

		foreach ($dsn_config as $dsn => $cfg)
		{
			if ('mysql' == $cfg['type'] || "redis" == $cfg["type"])
			{
				//if type is mysql,merge server config to dsn config
				$cfg = array_merge($cfg, $server_config[$cfg['server_id']]);
                $cfg["failover_server"] = $server_config[$cfg['failover_server_id']];
			}

            if ('mysql' == $cfg["type"] && SC::get("board_config.developer"))
            {
                $cfg["dbname"] = SC::get("board_config.developer")."_".$cfg["dbname"];
            }

			self::$dsn_settings[$dsn] = $cfg;
		}

		self::$populated = true;
        //NICED_LOG(LC::get("database.pdo.log_level"), NICED." ".__CLASS__." dsn config is ".var_export(self::$dsn_settings, true));

		return true;
	}
	
	/**
	 * @desc create db object
	 * @param $dsn
	 * @return NICED_PDO
	 */
    public static function create($dsn = null) 
	{
        $link = NULL;

        $db_setting = self::getDsnSetting($dsn);

        if (!$db_setting || !is_array($db_setting))
        {
            throw new NICED_Exception("db dsn ".$dsn." not found");
        }

        $key = $dsn;//md5(implode(",", $db_setting));
    	if (isset(self::$_connections[$key])) 
		{
    		return self::$_connections[$key];
    	}


		$driver = isset($db_setting['driver']) ? strtolower($db_setting['driver']) : self::DRIVER_TYPE_MYSQLI;

        if (!in_array($driver, self::$drivers))
        {
            $driver = self::DRIVER_TYPE_MYSQLI; 
        }

    	try
		{
			switch ($driver)
			{
				case self::DRIVER_TYPE_MYSQLI :
				{
					$class_name = 'NICED_MySQLi';
					break;				
				}
				case self::DRIVER_TYPE_PDO :
				{
					$class_name = 'NICED_PDO';
					break;				
				}	
				default :
				{
					//$class_name = 'NICED_MySQLi';
                    throw new NICED_Exception("not supported db driver");
				}
			}
			
    		//$connect = new NICED_MySQLi($ip, $user, $pass, $dbname, $port);
			if (!class_exists($class_name))
			{
				throw new NICED_Exception($class_name.' class not exists');
			}

            if (self::DRIVER_TYPE_PDO == $driver) //for pdo fail over
            {
                $error_code = NULL;

                for ($i = 0; $i <= LC::get("database.pdo.retry"); $i++)
                {
                    try
                    {
                        $link = new $class_name($dsn, $db_setting);
                        $link->getConnect();
                        break;
                    }
                    catch (Exception $e)
                    {
                        if (($error_code = $e->getErrorCode()) && 2003 == $error_code)
                        {
                            NICED_ALARM("pdo_connect_error");
                            continue;
                        }
                        else
                        {
                            NICED_ALARM("pdo_connect_fail");
                            throw $e;
                        }
                    }
                }
                
                if ($error_code)
                {
                    $db_setting["use_failover"] = true;
                    $exception = NULL;
                    NICED_ALARM("mysql_server_failover");
                    
                    for ($i = 0; $i <= LC::get("database.pdo.retry"); $i++)
                    {
                        try
                        {
                            $link = new $class_name($dsn, $db_setting);
                            $link->getConnect();
                        }
                        catch (NICED_DatabaseException $e)
                        {
                            if (($error_code = $e->getErrorCode()) && 2003 == $error_code)
                            {
                                $exception = $e;
                                NICED_ALARM("pdo_connect_error");
                                continue;
                            }
                            else
                            {
                                NICED_ALARM("pdo_connect_fail");
                                throw $e;
                            }
                        }
                    }

                    if ($exception)
                    {
                        NICED_ALARM("pdo_connect_fail");
                        throw $exception; 
                    }
                }
                else
                {
                    if (isset($retry))
                    {
                        NICED_ALARM('pdo_connect_pdo_succ_after_retry');
                    }
                }

            }
            else //other driver
            { 
                $link = new $class_name($dsn, $db_setting);
            }
        } 
        catch (Exception $e) 
        {
            throw new NICED_DatabaseException('from db error: '.$e->getMessage().", db config: ".var_export($db_setting, true));
    	}

    	self::$_connections[$key] = $link;
		unset($link);

    	return self::$_connections[$key];
    }

	public static function getDsnSetting($dsn) 
	{
        //resolve config first
        self::resolve();

		if (!$dsn) 
		{
			$dsn = 'default';
		} 
		else 
		{
			$vars = explode('.', $dsn);
			$dsn = $vars[0];
		}

		$dsn_settings = &self::$dsn_settings;
    	$dsn_setting   = isset($dsn_settings[$dsn]) ? $dsn_settings[$dsn] : array();

        if (!$dsn_setting)
        {
            $method = "get".ucfirst($dsn)."Settings";
            if (method_exists(__CLASS__, $method))
            {
                $dsn_setting = self::$method();
            }
        }
		
		return $dsn_setting;
	}

    public static function setDsnSetting($dsn, $key, $value)
    {
		if (!$dsn) 
		{
            return true;
		} 
		else 
		{
			$vars = explode('.', $dsn);
			$dsn = $vars[0];
		}

        if (!isset(self::$dsn_settings[$dsn]))
        {
            return true;
        }
        
        self::$dsn_settings[$dsn][$key] = $value;
		
		return true;
    }

	public static function closeAll() 
	{
		$connectors = self::$_connections;

        if(empty($connectors)) 
		{
			return;	
		}
		
        foreach(array_keys($connectors) as $key)
        {
            if(is_object($connectors[$key]) && method_exists($connectors[$key], 'close')) $connectors[$key]->close();
            unset(self::$_connections[$key]);
        }
	}

    private function getDefaultSettings()
    {
        return array("ip" => "127.0.0.1", "port" => "3306",  "dbname" => "admin_test", "user" => "root", "pass" => "", "type" => "mysql");
    }
}

register_shutdown_function(array('NICED_DBFactory', 'closeAll'));
